use super::{
    err, err_value, json, throw_err, Base, Client, ECode, MapSv, RNull, TBase, VBase, VMap, Value,
    VecV,
};
use std::collections::HashSet;

pub struct Buf {
    line: String,
    inbuf: String,
    outbuf: String,
}

impl Base for Buf {}
impl TBase for Buf {}

impl Buf {
    pub fn new(line: &str, sel_sn: &str, tp: Option<&str>) -> Self {
        let n = match tp {
            Some(v) => v,
            None => "buf",
        };

        Self {
            line: line.to_string(),
            inbuf: format!("in{}_{}", n, sel_sn),
            outbuf: format!("out{}_{}", n, sel_sn),
        }
    }

    pub async fn get_set(&self, tp: &str, client: &Client) -> HashSet<String> {
        let mut set = HashSet::new();

        if let Ok(body) = self.list(tp, client).await {
            for d in body.iter() {
                if let Some(pos) = d.k_str("to") {
                    set.insert(pos.to_string());
                }
            }
        }

        set
    }

    pub async fn list(&self, tp: &str, client: &Client) -> Result<VecV, Value> {
        let cmd = json!({ "line": self.line });
        let p = self.get_path(tp)?;

        let body = client.cfgdb(&cmd, &format!("field/list/{p}")).await?;
        let body = body.ck_array("data")?;

        Ok(body.clone())
    }

    pub async fn clear(&self, tp: &str, client: &Client) -> RNull {
        let cmd = json!({ "line": self.line });
        let p = self.get_path(tp)?;

        client.cfgdb(&cmd, &format!("field/rmfile/${p}")).await?;

        Ok(())
    }

    pub async fn pop(&self, tp: &str, client: &Client) -> Result<MapSv, Value> {
        let mut body = self.list(tp, client).await?;

        let ret = body.remove(0);
        let ret = ret.ckobj()?;

        let cmd = json!({ "line": self.line, "v": body });
        let p = self.get_path(tp)?;

        client.cfgdb(&cmd, &format!("field/md/${p}")).await?;

        Ok(ret.clone())
    }

    pub async fn put(&self, tp: &str, val: Value, client: &Client) -> RNull {
        let mut ret = Vec::new();

        if let Ok(mut body) = self.list(tp, client).await {
            ret.append(&mut body);
        };

        ret.push(val);

        let cmd = json!({ "line": self.line, "v": ret });
        let p = self.get_path(tp)?;

        client.cfgdb(&cmd, &format!("field/md/${p}")).await?;

        Ok(())
    }

    fn get_path(&self, tp: &str) -> Result<String, Value> {
        let path = match tp {
            "inner" => &self.inbuf,
            "outer" => &self.outbuf,
            _ => throw_err!(ECode::Para, "tp error"),
        };

        Ok(path.to_string())
    }
}
